#ifndef PAGE_COMPRESSION_H
#define PAGE_COMPRESSION_H

#include "c.h"
#include "storage/relfilenode.h"
#include "storage/bufpage.h"
#include "utils/rel.h"
#include "storage/buf_ext.h"

#define KPHC_CMP_BYTE_CONVERT_LEN 1
#define KPHC_CMP_DIFF_CONVERT_LEN 1
#define KPHC_CMP_PRE_CHUNK_LEN 3
#define KPHC_CMP_LEVEL_SYMBOL_LEN 1
#define KPHC_CMP_LEVEL_LEN 5
#define KPHC_CMP_ALGORITHM_LEN 3
#define KPHC_CMP_CHUNK_SIZE_LEN 2

#define KPHC_CMP_BYTE_CONVERT_INDEX 0
#define KPHC_CMP_DIFF_CONVERT_INDEX 1
#define KPHC_CMP_PRE_CHUNK_INDEX 2
#define KPHC_CMP_COMPERSS_LEVEL_SYMBOL_INDEX 3
#define KPHC_CMP_LEVEL_INDEX 4
#define KPHC_CMP_ALGORITHM_INDEX 5
#define KPHC_CMP_CHUNK_SIZE_INDEX 6

#define COMPRESS_ALGORITHM_PGLZ 1
#define COMPRESS_ALGORITHM_ZSTD 2
#define COMPRESS_ALGORITHM_PGZSTD 3
#define COMPRESS_ALGORITHM_ZLIB 4

#define KPHC_COMPRESS_ALGORITHM_PGLZ 1
#define KPHC_COMPRESS_ALGORITHM_ZSTD 2
#define KPHC_COMPRESS_ALGORITHM_ZLIB 4
#define KPHC_COMPRESS_UNSUPPORTED_ERROR (-2)
#define KPHC_IS_COMPRESSED_MAINFORK(reln, forkNum) ((reln)->smgr_rnode.node.opt != 0 && (forkNum) == MAIN_FORKNUM)

#define DEFAULT_ZSTD_COMPRESSION_LEVEL (1)
#define MIN_ZLIB_COMPRESSION_LEVEL (1)
#define MAX_ZLIB_COMPRESSION_LEVEL (9)
#define DEFAULT_ZLIB_COMPRESSION_LEVEL (1)
#ifdef USE_ZSTD
#define MIN_ZSTD_COMPRESSION_LEVEL ZSTD_minCLevel()
#define MAX_ZSTD_COMPRESSION_LEVEL ZSTD_maxCLevel()
#endif
typedef size_t KPHC_CFS_STORAGE_TYPE;
typedef uint32 pc_chunk_number_t;

typedef struct CmpBitStuct {
    const unsigned int bitLen;
    const unsigned int mask;
    const unsigned int moveBit;
}KPHCCmpBitStuct;

typedef struct relFileCompressOption {
    unsigned byteConvert : KPHC_CMP_BYTE_CONVERT_LEN;
    unsigned diffConvert : KPHC_CMP_DIFF_CONVERT_LEN;
    unsigned compressPreallocChunks : KPHC_CMP_PRE_CHUNK_LEN;
    unsigned compressLevelSymbol : KPHC_CMP_LEVEL_SYMBOL_LEN;
    unsigned compressLevel : KPHC_CMP_LEVEL_LEN;
    unsigned compressAlgorithm : KPHC_CMP_ALGORITHM_LEN;
    unsigned compressChunkSize : KPHC_CMP_CHUNK_SIZE_LEN;
}KPHCRelFileCompressOption;

enum KPHC_SMGR_READ_STATUS {
    SMGR_RD_OK = 0,
    SMGR_RD_NO_BLOCK = 1,
    SMGR_RD_CRC_ERROR = 2
};

typedef struct PageCompressData {
    char page_header[SizeOfPageHeaderData];
    uint32 crc32;
    uint32 size : 16;
    uint32 byte_convert : 1;
    uint32 diff_convert : 1;
    uint32 algorithm : 4;
    uint32 unused : 10;
    char data[FLEXIBLE_ARRAY_MEMBER];
} KPHCPageCompressData;

typedef struct HeapPageCompressData {
    char page_header[SizeOfHeapPageHeaderData];
    uint32 crc32;
    uint32 size : 16;
    uint32 byte_convert : 1;
    uint32 diff_convert : 1;
    uint32 algorithm : 4;
    uint32 unused : 10;
    char data[FLEXIBLE_ARRAY_MEMBER];
} KPHCHeapPageCompressData;

#define SET_COMPRESS_OPTION(node, byteConvert, diffConvert, preChunks, symbol, level ,algorithm, chunkSize) \
    do {                                                                                                    \
        (node).opt = 0;                                                                                     \
        (node).opt = (node).opt << gCmpBitStruct[KPHC_CMP_BYTE_CONVERT_INDEX].bitLen;                       \
        (node).opt += (byteConvert)&gCmpBitStruct[KPHC_CMP_BYTE_CONVERT_INDEX].mask;                         \
        (node).opt = (node).opt << gCmpBitStruct[KPHC_CMP_DIFF_CONVERT_INDEX].bitLen;                       \
        (node).opt += (diffConvert)&gCmpBitStruct[KPHC_CMP_DIFF_CONVERT_INDEX].mask;                         \
        (node).opt = (node).opt << gCmpBitStruct[KPHC_CMP_PRE_CHUNK_INDEX].bitLen;                          \
        (node).opt += (preChunks)&gCmpBitStruct[KPHC_CMP_PRE_CHUNK_INDEX].mask;                              \
        (node).opt = (node).opt << gCmpBitStruct[KPHC_CMP_COMPERSS_LEVEL_SYMBOL_INDEX].bitLen;              \
        (node).opt += (symbol)&gCmpBitStruct[KPHC_CMP_COMPERSS_LEVEL_SYMBOL_INDEX].mask;                     \
        (node).opt = (node).opt << gCmpBitStruct[KPHC_CMP_LEVEL_INDEX].bitLen;                              \
        (node).opt += (level)&gCmpBitStruct[KPHC_CMP_LEVEL_INDEX].mask;                                      \
        (node).opt = (node).opt << gCmpBitStruct[KPHC_CMP_ALGORITHM_INDEX].bitLen;                          \
        (node).opt += (algorithm)&gCmpBitStruct[KPHC_CMP_ALGORITHM_INDEX].mask;                              \
        (node).opt = (node).opt << gCmpBitStruct[KPHC_CMP_CHUNK_SIZE_INDEX].bitLen;                         \
        (node).opt += (chunkSize)&gCmpBitStruct[KPHC_CMP_CHUNK_SIZE_INDEX].mask;                             \
    } while (0)

#define SUPPORT_COMPRESSED(relKind, relam) \
    ((relKind) == RELKIND_RELATION || ((relKind) == RELKIND_INDEX && (relam) == BTREE_AM_OID))

#define REL_SUPPORT_COMPRESSED(relation) SUPPORT_COMPRESSED((relation)->rd_rel->relkind, (relation)->rd_rel->relam)

extern const KPHC_CFS_STORAGE_TYPE COMMON_STORAGE;

extern const uint32 INDEX_OF_HALF_BLCKSZ;
extern const uint32 INDEX_OF_QUARTER_BLCKSZ;
extern const uint32 INDEX_OF_EIGHTH_BLCKSZ;
extern const uint32 INDEX_OF_SIXTEENTHS_BLCKSZ;


#define KPHC_CFS_EXTENT_SIZE 129
#define KPHC_CFS_LOGIC_COUNT_PER_FILE (RELSEG_SIZE / KPHC_CFS_EXTENT_SIZE)
#define KPHC_CFS_LOGIC_BLOCKS_PER_FILE (KPHC_CFS_LOGIC_BLOCKS_PER_EXTENT * KPHC_CFS_LOGIC_COUNT_PER_FILE)
#define KPHC_CFS_LOGIC_BLOCKS_PER_EXTENT (KPHC_CFS_EXTENT_SIZE - 1)

#define KPHC_CFS_MAX_LOGIC_CHUNKS_NUMBER(chunk_size) (KPHC_CFS_LOGIC_BLOCKS_PER_EXTENT * (BLCKSZ / (chunk_size)))

extern const int KPHC_EXTENT_OPEN_FILE;
extern const int KPHC_WRITE_BACK_OPEN_FILE;
extern const int KPHC_EXTENT_CREATE_FILE;

extern const KPHCCmpBitStuct gCmpBitStruct[];

extern const uint32 CHUNK_SIZE_LIST[4];
extern KPHCCfsLocationConvert cfsLocationConverts[];

uint8 ConvertChunkSize(uint32 compressedChunkSize, bool *success);

void SetupPageCompressForRelation(RelFileNode *node, PageCompressOpts *compress_options, const char *relationName);

KPHCExtentLocation KPHCStorageConvert(SMgrRelation sRel, ForkNumber forknum, BlockNumber logicBlockNumber, bool skipSync, int type);

int KPHCCompressPage(const char* src, char* dst, int dst_size, const KPHCRelFileCompressOption option);
char *KPHCCfsCompressPage(const char *buffer, KPHCRelFileCompressOption *option, uint8 *nchunks);
int KPHCDecompressPage(const char* src, char* dst);

void KPHCAnalyzeCompressOptions(const RelFileNode node, KPHCRelFileCompressOption* opt);
off_t OffsetOfPageCompressChunk(uint16 chunkSize, int chunkNo);
KPHCCfsExtentAddress *KPHCGetExtentAddress(KPHCCfsExtentHeader *header, uint16 blockOffset);

size_t KPHCCfsWritePage(SMgrRelation reln, ForkNumber forknum, BlockNumber logicBlockNumber, char *buffer, bool sipSync, KPHC_CFS_STORAGE_TYPE type);
int KPHCCfsReadPage(SMgrRelation reln, ForkNumber forknum, BlockNumber logicBlockNumber, char *buffer, KPHC_CFS_STORAGE_TYPE type);
off_t KPHCCfsMdTruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber logicBlockNumber, bool skipSync, KPHC_CFS_STORAGE_TYPE type);

#endif // PAGE_COMPRESSION_H